Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify treatment of built-in functions and SAMs #4971

Merged
merged 36 commits into from Mar 31, 2016

Conversation

adriaanm
Copy link
Contributor

Function literal can have user-defined SAM or built-in function type.

A function literal, such as x => x is now accepted when a
Single Abstract Method (SAM) type is expected, in addition to
the usual Scala function type (e.g., Int => Int). A SAM type
is Java 8's convention for functions, so that e.g., Runnable
is now recognized as a function type.

Traditionally, Scala has shipped with FunctionN classes to
represent functions, with Int => Int being syntactic sugar
for Function1[Int, Int]. Scala 2.11 introduced experimental
support for expanding function literals to anonymous subclasses
of SAM type, and with Scala 2.12.0-M4, we now compile these
literals to the same bytecode as Java 8, whether targeting a
FunctionN or a user-defined functional interface.

Thanks to our new trait encoding where a trait compiles to
a Java 8 interface, our built-in FunctionN traits now also
compile to Functional Interfaces, so that Java programmers
can use Java 8's syntax for closures to target them.

If your project defines an implicit conversion from a SAM type
to a compatible Scala FunctionN type, this conversion will
no longer trigger, as the compiler performs SAM conversion
before looking for an implicit view, which enables generating
more efficient bytecode (using invokedynamic & LambdaMetaFactory).
To retain the old behavior, you may compile under -Xsource:2.11,
or disqualify the type from being a SAM (e.g., by adding a
second abstract method).

For example,

trait MySam { def apply(x: Int): String }
implicit def fun2sam(fun: Int => String): MySam = new MySam { def apply(x: Int) = fun(x) } // not used! 
val sammy: MySam = (x: Int) => x.toString

Here, SAM conversion from the function literal (x: Int) => x.toString
to the expected SAM type MySam takes precedence over inserting
the implicit view fun2sam((x: Int) => x.toString), which is how the
2.11 type checker expands the last line of the example.

Examples: Source Incompatible Change

Here are some snippets that compile in 2.11, but fail in 2.12. Roughly speaking, this is because SAM types and FunctionN types now compete harder during overload resolution. The Function type still wins (as explained in the spec update), but type inference breaks down when we cannot reduce the overloaded candidates down to one before type checking the arguments:

In the 1MLoC of the community build, we had less than a handful of lines that needed to change. Here's an example from akka-actor:

abstract class ActorRef extends java.lang.Comparable[ActorRef]

class Index[V](val valueComparator: java.util.Comparator[V]) {
  def this(cmp: (V, V) ⇒ Int) = this(new java.util.Comparator[V] {
    def compare(a: V, b: V): Int = cmp(a, b)
  })
}

object MessageDispatcher {
  // does not type check: call overloaded ctor...
  // new Index[ActorRef](_ compareTo _)
  // either pick an alternative explicitly
  new Index[ActorRef]((_ compareTo _): ju.Comparator[ActorRef]))
  // or provide explicit argument types:
  new Index[ActorRef]((_: ActorRef) compareTo (_: ActorRef))
}

Implementation Details and Restrictions

Scala considers any top-level class (trait or not) with exactly
one abstract method and an accessible zero-argument a SAM type.
(See the language specification for more details.)

On the Java 8 platform, only a subset of our SAM types can be
compiled to a combination of invokedynamic and LambdaMetaFactory,
to avoid having to emit bytecode for the anymous class at
compile time. Since Java 8's LambdaMetaFactory (LMF) is not
aware of Scala's trait encoding, it can only properly
instantiate traits that correspond to "pure" Java interfaces --
that is, interfaces that do not require synthetic members to
be implemented (by the compiler) when inherited in a proper class.

This rules out nested traits, traits with fields, traits that
extend a non-trait class (except for Object), traits with any
statements in their template body (for example, LMF would
not be able to run the initializer that's required to
execute the println in trait T { println("hello!") }),

Traits that cannot be instantiated by LMF are still SAM targets,
the compiler simply creates anonymous subclasses as before,
instead of deferring spinning them up at linkage time
(when invokedynamic's bootstrap method calls LMF).

Finally, when a SAM type is specialized (more precisely,
its specialized type parameter is instantiated with a
type for which it is specialized), we do not use
LambdaMetaFactory as incompatible with Scala specialization.

Specialization

The current specialization scheme is not amenable to using
LambdaMetaFactory to spin up subclasses. Since the generic
method is abstract, and the specialized ones are concrete,
specialization is rendered moot because we cannot implement
the specialized method with the lambda using LMF.

For the specialized versions of built-in function types,
we use hand-crafted versions, as illustrated next.
Notice how apply$mcB$sp is looking pretty SAMmy:

@FunctionalInterface
public interface JFunction0$mcB$sp extends JFunction0 {
    @Override
    public byte apply$mcB$sp();

    @Override
    default public Object apply() {
        return BoxesRunTime.boxToByte(this.apply$mcB$sp());
    }
}

Contrast this with our specialized standard FunctionN:

public interface Function0<R> {
    public R apply();

    default public byte apply$mcB$sp() {
        return BoxesRunTime.unboxToByte(this.apply());
    }
}

public interface Function0$mcB$sp extends Function0<Object> { }

The single abstract method in Function0$mcB$sp is apply, and
the method that would let us avoid boxing, if it were abstract,
is apply$mcB$sp...

Type Checker Internals

A function node is first type checked, and parameter types are
inferred, regardless of whether the expected function type is one
of our built-in FunctionN classes, or a user-defined Single
Abstract Method type (argument types are derived from both kinds
of expected types).

typedFunction always assigns a built-in FunctionN type to the
tree, though.

Next, if the expected type is a (polymorphic) SAM type, this
creates a tension between the tree's type and the expect type.
This gap is closed by the adapt method using inferSamType,
which attaches a SAMFunction attachment to the Function node,
so that the back-end can target the right method & type when
emitting the invokedynamic instruction.

We may want to improve this implementation to avoid re-typechecking
function nodes to expected SAM types, as this is pretty tricky
to juggle between erasure and specialization retypechecking trees.

Tree Shape of an expanded Function literal

As with the delambdafy phase, a function literal's body is lifted
out to a method in the surrounding scope (the "target method"),
with the actual function instance created from a lightweight
subclass of the expected type (or the corresponding
invokedynamic + LMF combo).

TODO 2.0

  • confirm scala-js is happy: 8e32d00 should have all the needed fixes
  • Using eta-expansion on a by-name parameter where the expected type is a SAM with 0 argument produces invalid code: the Function0 is cast to the SAM type, rather than adapting it.

TODO M5

  • javac only adds serialization support to LMF-based lambdas when the target type already inherits Serializable. Not sure which way we should go here.
  • should we print SAM functions differently from regular functions? One idea would be to print them as a Typed(fun, typeAtCurrentPhase(fun.attachments.head.samTpe)). Right now, we need to use -Xprint-types to see the difference.
  • We should give guidance to folks who are pattern matching on Function nodes in macros or compiler plugins (perhaps via quasiquotes) that they will need to discriminate based on the tree type if they only want to work with bona fide functions.
  • specialization???
  • remove scala/runtime/java8/JFunction* (need new STARR?)

@scala-jenkins scala-jenkins added this to the 2.12.0-M4 milestone Feb 17, 2016
@@ -3215,7 +3146,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// less expensive than including them in inferMethodAlternative (see below).
def shapeType(arg: Tree): Type = arg match {
case Function(vparams, body) =>
functionType(vparams map (_ => AnyTpe), shapeType(body))
functionType(vparams map (_ => AnyTpe), shapeType(body)) // TODO: should this be erased when retyping during erasure?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we shouldn't be doing overload resolution at that point, as we don't clear symbols of Select-s before retyping.

However, I did spot a place where overload resolution does happen late in the compiler. In Mixin:

          def staticCall(target: Symbol) = {
            def implSym = implClass(sym.owner).info.member(sym.name)
            assert(target ne NoSymbol,
              List(sym + ":", sym.tpe, sym.owner, implClass(sym.owner), implSym,
                  beforePrevPhase(implSym.tpe), phase) mkString " "
            )
            typedPos(tree.pos)(Apply(staticRef(target), transformSuper(qual) :: args))
          }

Here, the member call can return an overloaded symbol, and we hope and pray that overload resolution under the erased types finds the right method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No wait, implSym is only used in a assertion failure message. False alarm.

Tested with:

scala> trait T { private def foo(a: Option[Int], b: Any) = 1; private def foo(a: Option[String], b: String) = 2; def bar = foo(Some[Int](1), "") }
defined trait T

scala> new T{}.bar
res1: Int = 1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I'll remove the comment.

@adriaanm
Copy link
Contributor Author

TODO list

@bvenners & @sjrd ask:

  • interaction with implicit search

@retronym suggests:

  • serialization
  • can a SAM become an un-SAM after typers? (--> Nested/local types) add tests
  • also, exclude specialized types
  • (deferred) interaction with miniboxing

from @lrytz:

  • setting to disable sam conversion (-Xsource:2.11)

  • tests: by name

  • junit test for bytecode emitted for various VC/Any/Unit combos

  • check overloading spec, add test for ToString example to test/files/pos/sammy_overload.scala

  • update comments in test/files/pos/t9449.scala

  • more tests based on https://gist.github.com/lrytz/ec4da087dc65e421eb7d

  • remove mention of <static> from spec

  • remove comment from + functionType(vparams map (_ => AnyTpe), shapeType(body)) // TODO: should this be erased when retyping during erasure?, replace with lrytz's clarification:

    We don't even get here during erasure - overloading resolution happens during type checking. During erasure, the condition above (fun.symbol.isOverloaded) is false.

  • observed failure in overloading?

scala> trait ToString { def convert(x: Int): String }
defined trait ToString

scala> object O {
     |   def m(x: Int => String): Int = 0
     |   def m(x: ToString): Int = 1
     | }
defined object O

scala> O.m(_.toString)
<console>:14: error: missing parameter type for expanded function ((x$1: <error>) => x$1.toString)
       O.m(_.toString)
           ^
  • figure out boxing (unit <-> Void etc)
Caused by: java.lang.invoke.LambdaConversionException: 
Type mismatch for lambda expected return: class java.lang.Object is not convertible to void

  • Test based on question by @bvenners regarding implicit view / sam conversion:
trait MySam { def apply(x: Int): String }

def statusQuo = {
  var ok = false
  implicit def fun2sam(fun: Int => String): MySam = { ok = true; new MySam { def apply(x: Int) = fun(x) } }
  val className = (((x: Int) => x.toString): MySam).getClass.toString
  assert(ok, "implicit conversion not called")
  assertContains(className, "$anon$")
  assertNotContains(className, "$$Lambda$")
}

def statusIndy = {
  val className = (((x: Int) => x.toString): MySam).getClass.toString
  assertContains(className, "$$Lambda$")
  assertNotContains(className, "$anon$")
}

@adriaanm
Copy link
Contributor Author

@retronym, could you elaborate on 6ad9b44 and how I should extend its logic to indy sammy? My guess would be that we don't need to adapt unless we're supplying a sam to java? EDIT: that's probably wrong, I'll unmelt brain tomorrow and take another stab. Hopefully this will be the last issue, then I'll work on adding test cases.

@retronym
Copy link
Member

@adriaanm If the anon-class version of the SAM function would have required a bridge method, we need to either rely on the bridge created by LambdaMetafactory, or we have to create an anonFun$adapted method to perform adaptations pass it to LambdaMetafactory.

The logic in that commit would need to be extended to compare the impl method param types with the corresponding param type of the interface method. Currently, the knowledge the FunctionN parameter and return types erase to ObjectTpe is hard coded in boxedType.

If any of these require:

  • unboxing of a boxed primitive
  • unboxing of a value class

... we need to emit the adapter method.

We also need an adapter if the return type needs to be adapted from void to BoxedUnit (e.g., to conform the the a interface method returning an reference type.)

@adriaanm
Copy link
Contributor Author

Thanks! I think that's it: I'll see about generalizing the decision about boxing of the SAM's parameters and result type.

@adriaanm adriaanm force-pushed the genbcode-delambdafy branch 3 times, most recently from bf1c34a to e8b58ac Compare February 21, 2016 08:04
@adriaanm
Copy link
Contributor Author

I have a good feeling about this last commit 🚀

@adriaanm
Copy link
Contributor Author

Only when using a bootstrapped compiler:

java.lang.ClassCastException: [Lscala.collection.mutable.ArraySortingTest$CantSortMe;
cannot be cast to scala.collection.IterableLike
    at scala.runtime.Tuple2Zipped$.forall$extension(Tuple2Zipped.scala:88)

Looking into what's different about Tuple2Zipped.

@adriaanm
Copy link
Contributor Author

I've been poking around without much luck. I suspected the logic in determining which result type to use for the bridge, but not sure. Still trying to find a good way to minimize the bytecode diff between bootstrapped (fails) and non-bootstrapped version (ok).

@adriaanm adriaanm added the release-notes worth highlighting in next release notes label Feb 23, 2016
@adriaanm adriaanm changed the title WIP: INDY sammy [ci: last-only] WIP: unify treatment of built-in functions and SAMs [ci: last-only] Feb 25, 2016
@adriaanm adriaanm force-pushed the genbcode-delambdafy branch 2 times, most recently from ffed567 to a0721a2 Compare February 25, 2016 22:44
@adriaanm adriaanm force-pushed the genbcode-delambdafy branch 2 times, most recently from 8c57d6e to 60ba12b Compare March 17, 2016 18:46
@adriaanm
Copy link
Contributor Author

@retronym, good catch on excluding nested/local types from SAM conversion. I haven't found an example of why @specialized types are problematic, though. Did you have one in mind? Also, not sure anymore what the issue with serialization was. Sorry!

@retronym
Copy link
Member

I haven't found an example of why @specialized types are problematic, though. Did you have one in mind?

I think I had in mind a correctness problem based on my attempts to use install default methods in scala.FunctionN and use them directly in our indylamba (rather than JFunctionN) , but I can't create an example for that yet.

However, there is a performance problem:

scala> :paste -raw
// Entering paste mode (ctrl-D to finish)

package test

trait F0[@specialized +R] extends AnyRef { self =>
  def apply(): R
}
// Exiting paste mode, now interpreting.

scala> :paste -raw
// Entering paste mode (ctrl-D to finish)

package test { class C { def x: test.F0[Int] = () => 42 } }

// Exiting paste mode, now interpreting.

scala> :javap -v test.C
  public test.F0<java.lang.Object> x();
    Code:
       0: invokedynamic #36,  0             // InvokeDynamic #0:apply:()Ltest/F0;
       5: areturn

scala> :javap -c test.C
...
  public static final int test$C$$$anonfun$1();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        42
         2: ireturn
      LineNumberTable:
        line 1: 0

  public test.C();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #44                 // Method java/lang/Object."<init>":()V
         4: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ltest/C;
      LineNumberTable:
        line 1: 0

  public static final java.lang.Object test$C$$$anonfun$1$adapted();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
    Code:
      stack=1, locals=0, args_size=0
         0: invokestatic  #46                 // Method test$C$$$anonfun$1:()I
         3: invokestatic  #52                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
         6: areturn
      LineNumberTable:
        line 1: 0
}
BootstrapMethods:
  0: #22 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #24 ()Ljava/lang/Object;
      #28 invokestatic test/C.test$C$$$anonfun$1$adapted:()Ljava/lang/Object;
      #24 ()Ljava/lang/Object;
      #29 3
      #30 1
      #32 scala/Serializable
      #33 0
  1: #61 invokestatic scala/runtime/LambdaDeserialize.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

Or, by dumping a stack trace within the lambda body (including Hidden Frames, #TIL), we see that we route through the boxing bridge test.F0.apply$mcI$sp

% sbt -J-XX:+UnlockDiagnosticVMOptions -J-XX:+ShowHiddenFrames consoleQuick

scala> :paste -raw
// Entering paste mode (ctrl-D to finish)

package test

trait F0[@specialized +R] extends AnyRef { self =>
  def apply(): R
}

// Exiting paste mode, now interpreting.


scala> val x: test.F0[Int] = () => { new Throwable().printStackTrace; 42 }
x: test.F0[Int] = $$Lambda$1872/725725269@3016ce06

scala> x()
java.lang.Throwable
    at $line3.$read$$iw$$iw$.$line3$$read$$iw$$iw$$$anonfun$1(<console>:11)
    at $line3.$read$$iw$$iw$.$line3$$read$$iw$$iw$$$anonfun$1$adapted(<console>:11)
    at $line3.$read$$iw$$iw$$$Lambda$1872/725725269.apply(<Unknown>:1000000)
    at test.F0.apply$mcI$sp(<pastie>:4)
    at $line4.$read$$iw$$iw$.<init>(<console>:13)

@adriaanm
Copy link
Contributor Author

Ok, it seems like enough of a corner case to start by excluding them. Can always open it up again.

@retronym
Copy link
Member

Serialization seems to work (although warrants a test)

scala> :paste -raw
// Entering paste mode (ctrl-D to finish)

package p2; class C { def test: Runnable = () => println("yo!") }

// Exiting paste mode, now interpreting.

scala> new p2.C().test
res1: Runnable = p2.C$$Lambda$1962/114058013@7f929677

scala>   def serializeDeserialize[T <: AnyRef](obj: T) = {
     |     import java.io._
     |     val buffer = new ByteArrayOutputStream
     |     val out = new ObjectOutputStream(buffer)
     |     out.writeObject(obj)
     |     val in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray))
     |     in.readObject.asInstanceOf[T]
     |   }
serializeDeserialize: [T <: AnyRef](obj: T)T

scala> serializeDeserialize(res1)
res2: Runnable = p2.C$$Lambda$2142/89459519@26ce1bdc

scala> :javap -c -private p2.C
Compiled from "<pastie>"
public class p2.C {
  public java.lang.Runnable test();
    Code:
       0: invokedynamic #36,  0             // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       5: areturn

  public static final void p2$C$$$anonfun$1();
    Code:
       0: getstatic     #44                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: ldc           #46                 // String yo!
       5: invokevirtual #50                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
       8: return

  public p2.C();
    Code:
       0: aload_0
       1: invokespecial #53                 // Method java/lang/Object."<init>":()V
       4: return

  private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);
    Code:
       0: aload_0
       1: invokedynamic #65,  0             // InvokeDynamic #1:lambdaDeserialize:(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;
       6: areturn
}

I wasn't sure whether the backend would add the $deserializeLambda$ method to classes that only contain SAM lambdas (but it does), and that LambdaDeserializer would handle SAM lambdas (it also does, no I remember that I have tests for this already in LambdaDeserializerTest.java.

@retronym
Copy link
Member

I wonder if we can/should find a way to make the same exclusion for @miniboxed types. Obviously this one can come in a followup change after consultation with @VladUreche

@adriaanm adriaanm force-pushed the genbcode-delambdafy branch 2 times, most recently from 12e2886 to fcc5fad Compare March 18, 2016 00:28
@sjrd
Copy link
Member

sjrd commented Mar 18, 2016

@adriaanm Er ... I would very much hope that the SAM treatment gets precedence over implicit conversion! What if there is no explicit type on the lambda parameter? The implicit would not be applicable, but the SAM treatment should. And that means that, if implicit takes precedence over SAM, the behavior would be different whether or not there's an explicit type on the parameter.

When recovering missing argument types for an
eta-expanded method value, rework the expected type
to a method type.
@adriaanm
Copy link
Contributor Author

@sjrd, thanks again for uncovering these bugs -- M4 will be much better for it! Could you try again and see how we're doing now? 8e32d00 should have all the fixes scala-js needs

@adriaanm
Copy link
Contributor Author

Thanks, @retronym, for the super thorough review! 🔍 I think I've addressed all your feedback. I hope the last commit won't break anything -- I think it's optional for M4. I'll re-run the community build and hopefully we'll get the 👍 for M4 tomorrow!

// type `m` directly (undoing eta-expansion of method m) to determine the argument types.
val ptUnrollingEtaExpansion =
if (paramsMissingType.nonEmpty && pt != ErrorType) fun.body match {
case Apply(meth, args) if (vparams corresponds args) { case (p, Ident(name)) => p.name == name case _ => false } =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took me a while to convince myself that this (existing) code was correct, usually matching on names like this is sketchy.

But there is no way that an Ident argument to the method can bind to anything other than the eponymous function parameter.

{ val a = ""; (a, b) => bar(a /*Ident(a) can't be the outer a*/, b) }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a note.

@sjrd
Copy link
Member

sjrd commented Mar 31, 2016

@sjrd, thanks again for uncovering these bugs -- M4 will be much better for it! Could you try again and see how we're doing now? 8e32d00 should have all the fixes scala-js needs

With the latest 5d7d644, the Scala.js base (main) test suite passes :-)
I'll set up a dummy PR to test the full CI, but it should be fine.

@sjrd
Copy link
Member

sjrd commented Mar 31, 2016

OK the Scala.js CI (mostly) passes. The only failing tests are ArrayBuilderTest.Unit_zeros_{inline,noinline} when run on the JVM, but I'm pretty sure this is unrelated to this PR. It is probably related to #4904, and it would be our job to fix our test, in that case.

So this PR is green from Scala.js' point of view.

Jason points out the recursion will be okay if
type checking the function inside the eta-expansion provides
fully determined argument types, as the result type is
not relevant for this phase of typedFunction.
@adriaanm
Copy link
Contributor Author

Community build is looking good! The one failure was expected.

---==  Execution Report ==---
Report from the dbuild run for dbuild: 
Project acyclic-----------------: FAILED (RuntimeException: )
Project akka--------------------: SUCCESS (unchanged, not rebuilt)
Project async-------------------: SUCCESS (unchanged, not rebuilt)
Project browse------------------: SUCCESS (unchanged, not rebuilt)
Project discipline--------------: SUCCESS (unchanged, not rebuilt)
Project fastparse---------------: DID NOT RUN (stuck on broken dependencies: acyclic)
Project genjavadoc--------------: SUCCESS (unchanged, not rebuilt)
Project genjavadoc-plugin-------: SUCCESS (unchanged, not rebuilt)
Project jawn--------------------: SUCCESS (unchanged, not rebuilt)
Project lift-json---------------: SUCCESS (unchanged, not rebuilt)
Project macro-compat------------: SUCCESS (unchanged, not rebuilt)
Project macro-paradise----------: SUCCESS (unchanged, not rebuilt)
Project mima--------------------: SUCCESS (unchanged, not rebuilt)
Project parboiled---------------: SUCCESS (unchanged, not rebuilt)
Project pimpathon---------------: SUCCESS (unchanged, not rebuilt)
Project play-twirl--------------: SUCCESS (project rebuilt ok)
Project Play2-core--------------: SUCCESS (project rebuilt ok)
Project sbinary-----------------: SUCCESS (unchanged, not rebuilt)
Project sbt---------------------: SUCCESS (unchanged, not rebuilt)
Project sbt-testng-interface----: SUCCESS (unchanged, not rebuilt)
Project scala-------------------: SUCCESS (unchanged, not rebuilt)
Project scala-js----------------: SUCCESS (project rebuilt ok)
Project scala-logging-api-------: SUCCESS (unchanged, not rebuilt)
Project scala-logging-slf4j-----: SUCCESS (unchanged, not rebuilt)
Project scala-partest-----------: SUCCESS (unchanged, not rebuilt)
Project scala-partest-interface-: SUCCESS (unchanged, not rebuilt)
Project scala-records-----------: SUCCESS (unchanged, not rebuilt)
Project scala-refactoring-------: SUCCESS (unchanged, not rebuilt)
Project scala-stm---------------: SUCCESS (unchanged, not rebuilt)
Project scala-swing-------------: SUCCESS (unchanged, not rebuilt)
Project scalacheck--------------: SUCCESS (unchanged, not rebuilt)
Project scalamock---------------: SUCCESS (unchanged, not rebuilt)
Project scalatest---------------: SUCCESS (unchanged, not rebuilt)
Project scalaz------------------: SUCCESS (unchanged, not rebuilt)
Project scalaz-stream-----------: SUCCESS (unchanged, not rebuilt)
Project scodec-bits-------------: SUCCESS (unchanged, not rebuilt)
Project scoverage---------------: SUCCESS (unchanged, not rebuilt)
Project shapeless---------------: SUCCESS (project rebuilt ok)
Project slick-------------------: SUCCESS (unchanged, not rebuilt)
Project sourcecode--------------: SUCCESS (unchanged, not rebuilt)
Project specs2_24---------------: SUCCESS (unchanged, not rebuilt)
Project specs2_36---------------: SUCCESS (project rebuilt ok)
Project spire-------------------: SUCCESS (project rebuilt ok)
Project spray-json--------------: SUCCESS (unchanged, not rebuilt)
Project spray-twirl-------------: SUCCESS (unchanged, not rebuilt)
Project twitter-util------------: SUCCESS (unchanged, not rebuilt)
Project utest-------------------: SUCCESS (project rebuilt ok)
Project zinc--------------------: SUCCESS (unchanged, not rebuilt)
>>> The dbuild result is------------: FAILED (failed: acyclic)
---==  End Execution Report ==---

@retronym
Copy link
Member

If your project defines an implicit conversion from a SAM type
to a compatible Scala FunctionN type, this conversions will
no longer trigger

s/conversions/conversion

"trigger for a literal function"

Perhaps a short example would be helpful here.

// The constructor only exists if the trait's template has statements.
// Sadly, we can't be more precise without access to the tree that defines the SAM's owner.
!sym.primaryConstructor.exists &&
(sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be .members (now that this method doesn't recurse?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still maps ok over the ancestors.

@retronym
Copy link
Member

LGTM

@adriaanm
Copy link
Contributor Author

yay

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-notes worth highlighting in next release notes
Projects
None yet
9 participants